/*
 * @Author: Petrichor 572752189@qq.com
 * @Date: 2022-11-11 16:36:11
 * @LastEditors: Petrichor 572752189@qq.com
 * @LastEditTime: 2022-11-11 20:45:55
 * @FilePath: \H5_肖祺彦_2022.11.11.28\js\EasyDate.js
 * @Description: EasyDate类 作为 原生 Date类的加强封装版本
    需求 能够向下兼容Date类 Date类的实例对象可以直接使用EasyDate进行包装
 * 
 * Copyright (c) 2022 by Petrichor 572752189@qq.com, All Rights Reserved. 
 */
(function (win) {
  //常量 默认格式化字符串
  const DEFAULT_FORMAT = "yyyy-mm-dd";

  const UNIT_TYPE = {
    m: "Month",
    d: "Date",
    y: "FullYear",
  };

  /**
   * @class: EasyDate
   * @description: 日历组件主对象
   * @param {String} offset  初识日期偏移量/日期
   * @param {*} options 扩展的定制化功能
   * @desc: 通过EasyDate 模拟接口 实现月份调取/日期格式化等增值API
   */
  class EasyDate {
    constructor(offset, options) {
      /*
        offset传入参数可能的情况:
        1. offset是日期形式字符串 "2020/02/09" => isDate =>"2020-02-09"  => this.base = new Date("2020-02-09")
        2. offset 是一个Date对象 this.base = new Date(offset) 包装一层防止引用污染
        3. offset 没传 或者参数能够转换为 false  0 '' 
        isDate处理 字符串日期 其他false情况 
          如果是false情况 => 返回当天的Date对象
          如果是 字符串日期 返回格式化后的字符串日期
      */

      /* 1.判断传入的偏移量是否是一个日期 */
      let date = EasyDate.isDate(offset);

      if (date) {
        this.base = new Date(date);
      }

      //直接使用offset判断是不是Date对象，是的话直接拷贝一份
      if (offset instanceof Date) {
        this.base = new Date(offset);
        return;
      }

      this.format = options?.format || DEFAULT_FORMAT;
    }
    /*----------------------------------------------------------------*/
    /** 数据格式化部分
    
    /**
     * @description: format 日期对象+模板字符串 日期格式化字符串
     * (date , 'yyyy-MM-dd') => "2020-08-10"
     * @param {Object Date} date 日期对象
     * @param {String} format 模板字符串
     * @RegExp: [/y+/gi,/m+/gi,/d+/gi]
     * @return {String} 格式化后的日期字符串
     */
    static format(date = new Date(), format = DEFAULT_FORMAT) {
      let regMap = {
        y: date.getFullYear(),
        m: EasyDate.toDouble(date.getMonth() + 1),
        d: EasyDate.toDouble(date.getDate()),
      };
      return Object.entries(regMap).reduce((acc, [reg, value]) => {
        return acc.replace(new RegExp(`${reg}+`, "gi"), value);
      }, format);
    }
    /**
     * @description: 判断是否是日期以及是否是标准格式
     * @param {String} dateStr 字符串日期
     * @param {String} format  标准字符串模板
     * @RegExp: [/y+/gi,/m+/gi,/d+/gi] , /(\d{4})[-|//\/\:](\d{2})[-|//\/\:](\d{2})/
     * @return: {String} 标准模板转换后的日期字符串
     */
    static isDate(dateStr, format = DEFAULT_FORMAT) {
      /* 如果没有传dateStr的话就创建一个今天 */
      if (!dateStr) {
        return EasyDate.format(new Date(), format);
      }
      /* 如果dateStr是EasyDate对象或者Date对象则直接返回 */
      if (dateStr instanceof EasyDate || dateStr instanceof Date) {
        return dateStr;
      }
      let pos = [];
      let regExps = [/d+/gi, /y+/gi, /m+/gi];
      regExps.forEach((regexp) => {
        format = format.replace(regexp, (match) => {
          pos.push(match.substr(0, 1));
          return `(\\d{${match.length}})`;
        });
      });
      //dateStr格式 必须是 4位数字+分隔符+2位数字+分隔符+2位数字 这种格式
      format = format.replace(/-/gi, "[-|///:]");
      new RegExp(`^${format}`);
//dateStr不管是什么格式分隔符 分隔符统一替换成 -
      return dateStr
        .replace(/[-|//\/\:]/gi, "-")
        .replace(/(\d+)-(\d+)-(\d+)/g, function (item, $1, $2, $3) {
          return `${
            ($1.length === 4 && $1) || ("20" + $1).substr(-4)
          }-${EasyDate.toDouble($2)}-${EasyDate.toDouble($3)}`;
        });
    }
    /*----------------------------------------------------------------*/
    //
    /**
     * @description: 关于disibiled 根据 参数 start , end 结果不同
        如果 start end都传参 不可选范围为 start > date <= end
        如果 只传start 不可选范围为 start > date
        如果 start end都不传 范围为 "今日" > date
     * @param {Object Date | String} date 日期对象
     * @param {Object EasyDate} today 今天日期对象
     * @param {Object EasyDate} start 日期不可选范围起始 
     * @param {Object EasyDate} end 日期不可选范围结束
     * @param {String} format 日期对象
     * @return {Array} 传入的日期对象对应月的每一天数据对象
     */
    static getDates(
      date,
      today = this,
      start = today,
      end = null,
      format = DEFAULT_FORMAT
    ) {
      
      today = !(today instanceof EasyDate) && new EasyDate(today);
      start = start && new EasyDate(start);
      end = end && new EasyDate(end);
      let month = date.getMonth();
      date = new Date(date);
      date.setDate(1);
      let dates = [];

      //如果date日期月份没有变化
      while (date.getMonth() === month) {
        let label = EasyDate.format(date, format);
        dates.push({
          date: label.substr(0, 10), //2020-10-10 截取有效日期字符
          today: today && today.toString() === label, //对比格式化后字符串
          //如果end传参就加入条件 start<label>end 如果end没传 只判断 start < label 结果为true 不可选中
          disabled:
            start &&
            label > start.toString() &&
            (end ? label <= end.toString() : true),
        });
        date.setDate(date.getDate() + 1);
      }
      return dates;
    }

    /*----------------------------------------------------------------*/

    /**
     * @method: resetTime 重置日期时间
     * @global: 重置日期对象的时间为 0时0分0秒0毫秒
     * @desc: 防止后续日期对象比较运算 或者 +- 运算出现bug
     */
    resetTime() {
      this.base.setHours(0);
      this.base.setMinutes(0);
      this.base.setSeconds(0);
      this.base.setMilliseconds(0);
    }

    /**
     * @method: setDate 设置当前日期为传入日期
     * @params {Object} date 日期对象
     */
    setDate(date) {
      this.base.setDate(date);
    }

    /**
     * @method: getDate 返回当前日期对象
     * @return {Object} date 当前日期对象
     */
    getDate(date) {
      return this.base;
    }

    /**
     * @description: 判断当前年份是不是闰年
     * @return {Boolean} 返回true/false
     * @param year
     */
    static isLeapYear(year) {
      if (!year || year <= 1900) {
        return false;
      }
      if (year % 100 === 0) {
        return year % 400 === 0;
      }
      return year % 4 === 0;
    }

    /**
     * @description: 获取月份第一天
     * @return {Number} firstDay
     */
    getFirstDayOfThisMonth() {
      // 获取1号的星期几
      let date = this.clone();
      date.setDate(1);
      return date.getDay();
    }

    /**
     * @description: 克隆当前日期EasyDate对象
     * @return {Object EasyDate} new EasyDate
     */
    clone() {
      return new EasyDate(this.base, {
        format: this.format,
      });
    }

    /**
     * @description: 获取当前日
     * @return {Number} 星期 getDay
     */
    getDay() {
      return this.base.getDay();
    }

    /**
     * @description: transDate
     * @param {String} offset 偏移量 例如: "+1m" 表示下一个月
     */
    transDate(offset) {
      //参数转义
      offset = EasyDate.parse(offset);
      if (!offset) {
        return false;
      }
      for (let key in offset) {
        if (offset.hasOwnProperty(key)) {
          let type = UNIT_TYPE[key];
          this.base[`set${type}`](this.base[`get${type}`]() + offset[key]);
        }
      }
    }

    /**
     * @description: 日期偏移整理
     * @param {String} offset 偏移量 例如: "+1m" 表示下一个月
     * @return {Array} 对象数组
     */
    static parse(offset) {
      if (!offset) {
        return false;
      }
      //转换为小写格式
      offset = offset.toLowerCase();
      let result = {};
      //将+-和ymd切出来转换为对象数组
      offset.replace(/([+-]?\d+)([ymd])/g, (m, number, unit) => {
        result[unit] = Number(number);
      });
      return result;
    }

    /**
     * @description: 日历数据对象生成器
     * @param {Date} today 元数据的那一天
     * @param {Date} start 起始日期
     * @param {Date} end 结束日期
     * @return {Object:json} MonthData{year|month|empty|days}
     */
     toObject(today, start, end) {
      //this.base  是元数据日期对象
      const month = this.base.getMonth();
      return {
        year: String(this.base.getFullYear()),
        month: EasyDate.toDouble(month + 1),
        empty: this.getFirstDayOfThisMonth(),
        days: EasyDate.getDates(this.base, today, start, end, this.format),
      };
    }

    /**
     * @description: toString 格式化当前日期为文本
     * @return {String} 当前日期对象字符串
     */
    toString() {
      return EasyDate.format(this.base, this.format);
    }

    /**
     * @description: toDouble 十位补0 日期格式化工具方法
     * @param {Number} num 需要补0的数字
     * @return {String} 返回补0后的字符串
     */
    static toDouble(num) {
      return (String(num)[1] && String(num)) || "0" + num;
    }
    /*----------------------------------------------------------------*/
  }

  win.EasyDate = EasyDate;
})(window);
